import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt


def plot_demo(image):
    plt.hist(image.ravel(), 256, [0, 256])
    # numpy的ravel函数功能是将多维数组降为一维数组
    # hist是用来计算直方图的函数
    # 第一个参数x表示是一个数组或一个序列，是指定每个bin(箱子)分布的数据
    # 第二个参数bins表示指定bin(箱子)的个数,也就是总共有几条条状图
    # 第三个参数range表示箱子的下限和上限。即横坐标显示的范围，范围之外的将被舍弃。
    plt.show()


def image_hist(image):
    colors = ("blue", "green", "red")
    for i, color in enumerate(colors):  # 遍历集合及其下标
        hist = cv.calcHist([image], [i], None, [256], [0, 256])
        # 第一个参数images表示输入图像，传入时应该用中括号[ ]括起来
        # 第二个参数channels表示传入图像的通道，如果是灰度图像，那就不用说了，只有一个通道，值为0，如果是彩色图像（有3个通道），那么值为0,1,2,中选择一个，对应着BGR各个通道。这个值也得用[ ]传入。
        # 第三个参数mask表示掩膜图像。如果统计整幅图，那么为None。主要是如果要统计部分图的直方图，就得构造相应的掩膜来计算。
        # 第四个参数histSize表示灰度级的个数，需要中括号。
        # 第五个参数ranges表示像素值的范围，通常[0,256]。
        plt.plot(hist, color=color)  # 调整plot函数参数配置曲线样式、粗细、颜色、标记
        plt.xlim([0, 256])  # 设置x坐标轴刻度取值范围
    plt.show()


def equal_hist_demo(image):  # 直方图均衡化
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    cv.imshow("gray", gray)
    dst = cv.equalizeHist(gray)  # 直方图均衡化函数
    cv.imshow("equal_hist", dst)


def clahe_demo(image):  # 局部自适应均衡化
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    cv.imshow("gray", gray)
    clahe = cv.createCLAHE(clipLimit=2, tileGridSize=(8, 8))  # 局部自适应均衡化函数
    # 第一个参数是对比度大小
    # 第二个参数是模块大小
    dst = clahe.apply(gray)  # 将上述参数应用于gray图像
    cv.imshow("clahe", dst)


def creat_rgbhist(image):  # 创造直方图（将三维数组用一维数组表示）
    h, w, c = image.shape
    rgbhist = np.zeros([16*16*16, 1], np.float32)  # rgb每一层的灰度级为16，则一共有16^3种组合方式，创建一个16^3*1的矩阵
    bsize = 256/16  # 表示灰度级的个数
    for row in range(h):
        for col in range(w):
            b = image[row, col, 0]
            g = image[row, col, 1]
            r = image[row, col, 2]
            index = np.int(b/bsize)*16*16+np.int(g/bsize)*16+np.int(c)  # 计算某种灰度级组合的像素点在直方图中的位置
            rgbhist[np.int(index), 0] = rgbhist[np.int(index), 0] + 1
    return rgbhist


def hist_compare(image1, image2):
    hist1 = creat_rgbhist(image1)
    hist2 = creat_rgbhist(image2)
    match1 = cv.compareHist(hist1, hist2, cv.HISTCMP_BHATTACHARYYA)  # 计算巴氏距离，越小则相似度越高
    match2 = cv.compareHist(hist1, hist2, cv.HISTCMP_CORREL)  # 计算相关度，相关度越大（接近1），则相似度越高
    match3 = cv.compareHist(hist1, hist2, cv.HISTCMP_CHISQR)  # 计算卡方，卡方越小则相似度越高
    # 直方图这种比较方式有一个好处就是不用在意相比较的两个图像的大小差异，如果存在差异则对直方图进行归一化处理即可
    print("巴氏距离：%s， 相关性：%s， 卡方：%s" % (match1, match2, match3))


def hist2d_demo():  # 二维直方图
    sample = cv.imread("imgs/test008_sample.jpg")
    target = cv.imread("imgs/test008.jpg")
    cv.imshow("sample", sample)
    cv.imshow("target", target)
    sample_hsv = cv.cvtColor(sample, cv.COLOR_BGR2HSV)
    target_hsv = cv.cvtColor(target, cv.COLOR_BGR2HSV)
    samplehist = cv.calcHist([sample_hsv], [0, 1], None, [40, 5], [0, 180, 0, 256])  # hsv有两个通道
    cv.normalize(samplehist, samplehist, 0, 255, cv.NORM_MINMAX)  # 归一化
    # 第一个参数src表示输入数组。
    # 第二个参数dst表示输出与src相同大小的数组，支持原地运算。
    # 第三个参数alpha表示range normalization模式的最小值。
    # 第四个参数beta表示range normalization模式的最大值，不用于norm normalization(范数归一化)模式。
    # 第五个参数norm_type表示归一化的类型。norm_type参数可以有以下的取值：
    # NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围，线性归一化，一般较常用。
    # NORM_INF:归一化数组的C-范数(绝对值的最大值)。
    # NORM_L1 ：归一化数组的L1-范数(绝对值的和)。
    # NORM_L2 ：归一化数组的(欧几里德)L2-范数。
    dst = cv.calcBackProject([target_hsv], [0, 1], samplehist, [0, 180, 0, 256], 1)  # 直方图反向投影
    # 反向投影用于在输入图像（通常较大）中查找特定图像（通常较小或者仅1个像素，以下将其称为模板图像）最匹配的点或者区域，也就是定位模板图像出现在输入图像的位置。
    # 第一个参数images表示输入图像（是HSV图像）。传入时应该用中括号[ ]括起来。
    # 第二个参数channels表示用于计算反向投影的通道列表，通道数必须与直方图维度相匹配。
    # 第三个参数hist表示输入的模板图像直方图。
    # 第四个参数ranges表示直方图中每个维度bin的取值范围 （即每个维度有多少个bin）。
    # 第五个参数scale表示可选输出反向投影的比例因子，一般取1。
    cv.imshow("back_projection_demo", dst)


src = cv.imread("imgs/test008.jpg")
# src_ = cv.imread("imgs/test007.png")
# cv.imshow("zll", src)
# plot_demo(src)
# image_hist(src)
# clahe_demo(src)
# hist_compare(src, src_)
hist2d_demo()
cv.waitKey(0)
cv.destroyAllWindows()